;; C# code generators

(defun c#-block-size (block)
  (let* ((size 0) 
    (var-index 0)
    (declarations "")
    (body
      (with-output-to-string (s)
        (dolist (var-data (block-vars block))
          (cond
            ((eq (var-type var-data) '|Variable|)
              ;; We need a special case for variables with variable length (Strings)
              (let ((s-sofar (get-output-stream-string s)))
                (cond
                  ((> (length s-sofar) 0)
                    (setf declarations (format nil "~aint off~a = ~a;~%" declarations var-index s-sofar))
                    (setf s-sofar (format nil "off~a" var-index))
                    (setf var-index (+ 1 var-index))
                    (format s "~a+" s-sofar)))
                (format s "~a+" (+ size (var-type-arg var-data)))
                (ecase (var-type-arg var-data)
                  (1
                    (if (> (length s-sofar) 0) (format s "data[n+~a+~a]" s-sofar size) (format s "data[n+~a]" size)))
                  (2
                    (if (> (length s-sofar) 0)
                      (format s "(data[n+~a+~a]+(data[n+~a+~a]<<8))" s-sofar size s-sofar (+ 1 size))
                      (format s "(data[n+~a]+(data[n+~a]<<8))" size (+ 1 size)))))
                (setf size 0)))
            (t (incf size (var-size var-data))))))))
    (with-output-to-string (s)
;;      (dotimes (i var-index) (format s "int off~a;~%" i))
      (if (> (length body) 0)
        (format s "~aint off=~a+~a" declarations body size)
        (format s "~aint off=~a" declarations size)))))

(defun c#-declare-variable (var-data out-flag)
  (let ((n (var-name var-data))
        (d ""))
    (if out-flag (setf d "out "))
    (ecase (var-type var-data)
      (|BOOL| (format nil "~abool ~a" d n))
      (|Fixed| (format nil "~abyte[] ~a" d n))
      (|U16| (format nil "~aushort ~a" d n))
      (|S16| (format nil "~ashort ~a" d n))
      (|U64| (format nil "~aulong ~a" d n))
      (|LLVector3| (format nil "~afloat x~a, ~afloat y~a, ~afloat z~a" d n d n d n))
      (|LLVector4| (format nil "~afloat x~a, ~afloat y~a, ~afloat z~a, ~afloat s~a" d n d n d n d n))
      (|LLVector3d| (format nil "~adouble x~a, ~adouble y~a, ~adouble z~a" d n d n d n))
      (|LLQuaternion| (format nil "~afloat x~a, ~afloat y~a, ~afloat z~a, ~afloat w~a" d n d n d n d n))
      (|Variable| (format nil "~abyte[] ~a" d n))
      (|LLUUID| (format nil "~aGuid ~a" d n))
      (|IPADDR| (format nil "~auint ~a" d n))
      (|IPPORT| (format nil "~aushort ~a" d n))
      (|F32| (format nil "~afloat ~a" d n))
      (|F64| (format nil "~adouble ~a" d n))
      (|S8| (format nil "~asbyte ~a" d n))
      (|S32| (format nil "~aint ~a" d n))
      (|U8| (format nil "~abyte ~a" d n))
      (|U32| (format nil "~auint ~a" d n)))))

(defun c#-variable-to-udp (var-data)
  (let ((n (var-name var-data)))
    (ecase (var-type var-data)
      (|BOOL| (format nil "Bool_UDP(~a, data, ref n)" n))
      (|Fixed| (format nil "Fixed_UDP(~a, ~a, data, ref n)" n (var-type-arg var-data)))
      (|U16| (format nil "U16_UDP(~a, data, ref n)" n))
      (|S16| (format nil "S16_UDP(~a, data, ref n)" n))
      (|U64| (format nil "U64_UDP(~a, data, ref n)" n))
      (|LLVector3| (format nil "LLVector3_UDP(x~a, y~a, z~a, data, ref n)" n n n))
      (|LLVector4| (format nil "LLVector4_UDP(x~a, y~a, z~a, s~a, data, ref n)" n n n n))
      (|LLVector3d| (format nil "LLVector3d_UDP(x~a, y~a, z~a, data, ref n)" n n n))
      (|LLQuaternion| (format nil "LLQuaternion_UDP(x~a, y~a, z~a, w~a, data, ref n)" n n n n))
      (|Variable|
       (ecase (var-type-arg var-data)
         (1 (format nil "Variable1_UDP(~a, data, ref n)" n))
         (2 (format nil "Variable2_UDP(~a, data, ref n)" n))))
      (|LLUUID| (format nil "LLUUID_UDP(~a, data, ref n)" n))
      (|IPADDR| (format nil "U32_UDP(~a, data, ref n)" n))
      (|IPPORT| (format nil "IPPORT_UDP(~a, data, ref n)" n))
      (|F32| (format nil "F32_UDP(~a, data, ref n)" n))
      (|F64| (format nil "F64_UDP(~a, data, ref n)" n))
      (|S8| (format nil "S8_UDP(~a, data, ref n)" n))
      (|S32| (format nil "S32_UDP(~a, data, ref n)" n))
      (|U8| (format nil "U8_UDP(~a, data, ref n)" n))
      (|U32| (format nil "U32_UDP(~a, data, ref n)" n)))))

(defun c#-variable-from-udp (var-data)
  (let ((n (var-name var-data)))
    (ecase (var-type var-data)
      (|BOOL| (format nil "UDP_Bool(out ~a, data, ref n)" n))
      (|Fixed| (format nil "UDP_Fixed(out ~a, ~a, data, ref n)" n (var-type-arg var-data)))
      (|U16| (format nil "UDP_U16(out ~a, data, ref n)" n))
      (|S16| (format nil "UDP_S16(out ~a, data, ref n)" n))
      (|U64| (format nil "UDP_U64(out ~a, data, ref n)" n))
      (|LLVector3| (format nil "UDP_LLVector3(out x~a, out y~a, out z~a, data, ref n)" n n n))
      (|LLVector4| (format nil "UDP_LLVector4(out x~a, out y~a, out z~a, out s~a, data, ref n)" n n n n))
      (|LLVector3d| (format nil "UDP_LLVector3d(out x~a, out y~a, out z~a, data, ref n)" n n n))
      (|LLQuaternion| (format nil "UDP_LLQuaternion(out x~a, out y~a, out z~a, out w~a, data, ref n)" n n n n))
      (|Variable|
       (ecase (var-type-arg var-data)
         (1 (format nil "UDP_Variable1(out ~a, data, ref n)" n))
         (2 (format nil "UDP_Variable2(out ~a, data, ref n)" n))))
      (|LLUUID| (format nil "UDP_LLUUID(out ~a, data, ref n)" n))
      (|IPADDR| (format nil "UDP_U32(out ~a, data, ref n)" n))
      (|IPPORT| (format nil "UDP_IPPORT(out ~a, data, ref n)" n))
      (|F32| (format nil "UDP_F32(out ~a, data, ref n)" n))
      (|F64| (format nil "UDP_F64(out ~a, data, ref n)" n))
      (|S8| (format nil "UDP_S8(out ~a, data, ref n)" n))
      (|S32| (format nil "UDP_S32(out ~a, data, ref n)" n))
      (|U8| (format nil "UDP_U8(out ~a, data, ref n)" n))
      (|U32| (format nil "UDP_U32(out ~a, data, ref n)" n)))))

(defun c#-random-value (var-data use-libsl)
  (ecase (var-type var-data)
    (|BOOL| "(GetTestValue(s, r, 2)==0)")
    (|Fixed| (format nil "new byte[~a]" (var-type-arg var-data)))
    (|U16| "(ushort) GetTestValue(s, r, -1)")
    (|S16| "(short) GetTestValue(s, r, -1)")
    (|U64| "(ulong) GetTestValue(s, r, -1)")
    (|LLVector3|
     (if use-libsl
         "new Vector3(GetTestValue(s, r, -1), GetTestValue(s, r, -1), GetTestValue(s, r, -1))"
         "GetTestValue(s, r, -1), GetTestValue(s, r, -1), GetTestValue(s, r, -1)"))
    (|LLVector4|
     (if use-libsl
         "new Vector4(GetTestValue(s, r, -1), GetTestValue(s, r, -1), GetTestValue(s, r, -1), GetTestValue(s, r, -1))"
         "GetTestValue(s, r, -1), GetTestValue(s, r, -1), GetTestValue(s, r, -1), GetTestValue(s, r, -1)"))
    (|LLVector3d|
     (if use-libsl
         "new Vector3d(GetTestValue(s, r, -1), GetTestValue(s, r, -1), GetTestValue(s, r, -1))"
         "GetTestValue(s, r, -1), GetTestValue(s, r, -1), GetTestValue(s, r, -1)"))
    (|LLQuaternion|
     (if use-libsl
         "new Quaternion(GetTestValue(s, r, -1), GetTestValue(s, r, -1), GetTestValue(s, r, -1), GetTestValue(s, r, -1))"
         "GetTestValue(s, r, -1), GetTestValue(s, r, -1), GetTestValue(s, r, -1), GetTestValue(s, r, -1)"))
    (|Variable| "vs")
    (|LLUUID| 
     (if use-libsl 
         "new UUID((ulong) GetTestValue(s, r, -1))"
         "(new UUID((ulong) GetTestValue(s, r, -1))).Guid"))
    (|IPADDR| "(uint) GetTestValue(s, r, -1)")
    (|IPPORT| "(ushort) (GetTestValue(s, r, -1)%65536)")
    (|F32| "(float) GetTestValue(s, r, -1)")
    (|F64| "(double) GetTestValue(s, r, -1)")
    (|S8| "(sbyte) (GetTestValue(s, r, -1)%256-128)")
    (|S32| "GetTestValue(s, r, -1)")
    (|U8| "(byte) (GetTestValue(s, r, -1)%256)")
    (|U32| "(uint) GetTestValue(s, r, -1)")))

(defun c#-packet-size (size)
  ;; Size is a list block sizes, either integers (for Single/Multiple blocks), or a tuple (integer) for Variable
  (with-output-to-string (s)
    (format s "int n=~a;" (first size)) ;; header size
    (dolist (b (rest size))
      (cond
        ((listp b) (format s "n+=1+((int) data[n])*~a;" (first b)))
        (t (format s "n+=~a;" b))))))
  
(defun c#-calculate-offset (s packet block-name)
  (format s "  int n=~a;~%" (packet-header-size packet))
  (dolist (block (packet-blocks packet))
    (if (string= block-name (block-name block)) (return))
    (format s "  n+=~a_~a_Length(data);~%" (packet-name packet) (block-name block))))

(defun c#-header-flags (packet)
  (with-output-to-string (s)
    (format s "0")
    ;; There is no way to say from the template file if a message is reliable or not
    ;; (It is not using the Trusted/NotTrusted flag)
    ;; We have to assume that the program is setting RELIABLE using the SetReliable method)
    ;;(format s "Header.RELIABLE")
    (if (packet-zerocoded? packet) (format s "|Header.ZEROCODED"))))

(defun c#-packet-enums (parse-tree)
  "Create enums for all packet functions"
  (let ((indent "            "))
    (with-output-to-string (s)
      (format s "~apublic enum PacketType~%" indent)
      (format s "~a{~%" indent )
      (dolist (packet parse-tree)
        (case (packet-header-type packet)
          (|Low| (format s "~a    ~a = ~a,~%"  indent (packet-name packet) (+ #x10000 (packet-number packet))))
          (|Medium| (format s "~a    ~a = ~a,~%" indent (packet-name packet) (+ #x20000 (packet-number packet))))
          (|High| (format s "~a    ~a = ~a,~%" indent (packet-name packet) (+ #x30000 (packet-number packet))))))
      (format s "~a}~%" indent)

      (format s "~apublic static PacketType GetType(byte[] data)~%" indent)
      (format s "~a{~%" indent )
      (dolist (packet parse-tree)
        (format s "~a    if(GetPacketFrequency(data)==Frequency.~a && GetPacketID(data)==~a) return PacketType.~a;~%"
                indent (packet-header-type packet) (packet-number packet) (packet-name packet)))
      (format s "~a    throw new Exception(\"Unknown packet in GetType\");~%" indent)
      (format s "~a}~%" indent)
      
      (format s "~apublic static int GetLength(byte[] data)~%" indent)
      (format s "~a{~%" indent )
      (format s "~a    PacketType type = GetType(data);~%" indent)
      (dolist (packet parse-tree)
        (format s "~a    if(type==PacketType.~a) return ~aLength(data);~%"
                indent (packet-name packet) (packet-name packet)))
      (format s "~a    throw new Exception(\"Unknown packet in GetLength\");~%" indent)
      (format s "~a}~%" indent))))

(defun c#-packet-udp-functions (parse-tree)
  "create source code for the <packet>_UDP functions"
  (with-output-to-string (s)
    (dolist (packet parse-tree)
      (format t "Creating ~a (c# version)~%" (packet-name packet))
      (let ((header-size (packet-header-size packet)))
        (format s "public static void ~aHeader(byte[] data) { Header_UDP(data, ~a, Header.Frequency.~a, ~a); }~%"
                (packet-name packet) (packet-number packet) (packet-header-type packet) (c#-header-flags packet))
        (dolist (block (packet-blocks packet))
          (case (block-type block)
            (|Variable|
             (format s "public static void ~a_~aBlock(byte[] data, int length) {~%" (packet-name packet) (block-name block))
             (c#-calculate-offset s packet (block-name block))
             (format s "  data[n]=(byte) length;~%}~%")
             (format s "public static void ~a_~aBlock(byte[] data, int index" (packet-name packet) (block-name block))
             (dolist (var (block-vars block)) (format s ", ~a" (c#-declare-variable var nil)))
             (format s ") {~%")
             (c#-calculate-offset s packet (block-name block))
             (format s "  n+=1; for(int j=0; j<index; j++) { ~a; n+=off; }~%"  (c#-block-size block))
             (dolist (var (block-vars block)) (format s "  ~a;~%" (c#-variable-to-udp var)))
             (format s "}~%")
             (format s "public static int ~a_~aBlock(byte[] data) {~%" (packet-name packet) (block-name block))
             (c#-calculate-offset s packet (block-name block))
             (format s "  return (int) data[n];~%}~%")
             (format s "public static void ~a_~aBlock(byte[] data, int index" (packet-name packet) (block-name block))
             (dolist (var (block-vars block)) (format s ", ~a" (c#-declare-variable var t)))
             (format s ") {~%")
             (c#-calculate-offset s packet (block-name block))
             (format s "  n+=1; for(int j=0; j<index; j++) { ~a; n+=off; }~%"  (c#-block-size block))
             (dolist (var (block-vars block)) (format s "  ~a;~%" (c#-variable-from-udp var)))
             (format s "}~%")
             (format s "public static int ~a_~a_Length(byte[] data) {~%" (packet-name packet) (block-name block))
             (c#-calculate-offset s packet (block-name block))
             (format s "  int x=n, l=(int) data[n++];~%  for(int j=0; j<l; j++) { ~a; n+=off; }~%  return n-x;~%}~%"  (c#-block-size block)))
            (|Single|
             (format s "public static void ~a_~a(byte[] data" (packet-name packet) (block-name block))
             (dolist (var (block-vars block)) (format s ", ~a" (c#-declare-variable var nil)))
             (format s ") {~%")
             (c#-calculate-offset s packet (block-name block))
             (dolist (var (block-vars block)) (format s "  ~a;~%" (c#-variable-to-udp var)))
             (format s "}~%")
             (format s "public static void ~a_~a(byte[] data" (packet-name packet) (block-name block))
             (dolist (var (block-vars block)) (format s ", ~a" (c#-declare-variable var t)))
             (format s ") {~%")
             (c#-calculate-offset s packet (block-name block))
             (dolist (var (block-vars block)) (format s "  ~a;~%" (c#-variable-from-udp var)))
             (format s "}~%")
             (format s "public static int ~a_~a_Length(byte[] data) {~%" (packet-name packet) (block-name block))
             (c#-calculate-offset s packet (block-name block))
             (format s "  int x=n; ~a; n+=off; return n-x;~%}~%" (c#-block-size block)))
            (|Multiple|
             (format s "public static void ~a_~a(byte[] data, int index" (packet-name packet) (block-name block))
             (dolist (var (block-vars block)) (format s ", ~a" (c#-declare-variable var nil)))
             (format s ") {~%")
             (c#-calculate-offset s packet (block-name block))
             (format s "  ~a; n+=index*off;" (c#-block-size block))
             (dolist (var (block-vars block)) (format s "  ~a;~%" (c#-variable-to-udp var)))
             (format s "}~%")
             (format s "public static void ~a_~a(byte[] data, int index" (packet-name packet) (block-name block))
             (dolist (var (block-vars block)) (format s ", ~a" (c#-declare-variable var t)))
             (format s ") {~%")
             (c#-calculate-offset s packet (block-name block))
             (format s "  ~a; n+=index*off;" (c#-block-size block))
             (dolist (var (block-vars block)) (format s "  ~a;~%" (c#-variable-from-udp var)))
             (format s "}~%")
             (format s "public static int ~a_~a_Length(byte[] data) {~%" (packet-name packet) (block-name block))
             (c#-calculate-offset s packet (block-name block))
             (format s "  int x=n; ~a; n+=~a*off; return n-x;~%}~%" (c#-block-size block) (block-cardinality block)))))
        (format s "public static int ~aLength(byte[] data) {~%" (packet-name packet))
        (c#-calculate-offset s packet "")
        (format s "  return n;~%}~%")))))

(defun c#-unittest-functions (parse-tree) 
  (with-output-to-string (s)
    (dolist (packet parse-tree)
      (let ((name (packet-name packet)))
        (format s "static void Test~a()~%{~%" (packet-name packet))
        (format s "  Console.WriteLine(\"  ~a...\");~%" (packet-name packet))
        (format s "  // Make original libSL object~%  Random r=new Random(0);~%" name)
        (format s "  StreamWriter s=null;~%")
        (format s "  ~aPacket p1 = new ~aPacket();~%" name name)

        ;; libSL calls
        (dolist (block (packet-blocks packet))
          ;; libSL avoids a name clash with the Header function by changing Header to _Header
          (let ((bname (if (string= (block-name block) "Header") "_Header" (block-name block)))) 
            (ecase (block-type block)
              (|Single| 
                (dolist (var (block-vars block)) (format s "  p1.~a.~a=~a;~%" bname (first var) (c#-random-value var t))))
              (|Variable|
                (format s "  p1.~a = new ~aPacket.~aBlock[5];~%" bname name bname)
                (format s "  for(int i=0; i<5;i++) {~%")
                (format s "    p1.~a[i]=new ~aPacket.~aBlock();~%" bname name bname)
                (dolist (var (block-vars block)) (format s "    p1.~a[i].~a=~a;~%" bname (first var) (c#-random-value var t)))
                (format s "  }~%"))
              (|Multiple|
                (format s "  for(int i=0; i<~a;i++) {~%" (block-cardinality block))
                (format s "    p1.~a[i]=new ~aPacket.~aBlock();~%" bname name bname)
                (dolist (var (block-vars block)) (format s "    p1.~a[i].~a=~a;~%" bname (first var) (c#-random-value var t)))
                (format s "  }~%")))))
        (format s "  byte[] msg1=p1.ToBytes();~%  // Make new libSL object~%  r=new Random(0);~%")
        (format s "  byte[] msg2=new byte[1024];~%")
        (format s "  s=new StreamWriter(\"~a.log\", false);~%" (packet-name packet))
        (format s "  SLPacket.~aHeader(msg2);~%" name)

        ;; funSL calls
        (dolist (block (packet-blocks packet))
          (let ((bname (block-name block)))
            (ecase (block-type block)
              (|Variable|
                (format s "  SLPacket.~a_~aBlock(msg2, 5);~%" name bname)
                (format s "  for(int i=0; i<5;i++) {~%")
                (format s "    SLPacket.~a_~aBlock(msg2, i" name bname)
                (dolist (var (block-vars block)) (format s ", ~a" (c#-random-value var nil)))
                (format s ");~%  }~%"))
              (|Single|
                (format s "  SLPacket.~a_~a(msg2" name bname)
                (dolist (var (block-vars block)) (format s ", ~a" (c#-random-value var nil)))
                (format s ");~%"))
              (|Multiple|
                (format s "  for(int i=0; i<~a;i++) {~%" (block-cardinality block))
                (format s "    SLPacket.~a_~a(msg2, i" name bname)
                (dolist (var (block-vars block)) (format s ", ~a" (c#-random-value var nil)))
                (format s ");~%  }~%")))))
        (format s "  for(int i=0; i<SLPacket.~aLength(msg2); i++) s.WriteLine(msg2[i]);~%" (packet-name packet))
        (format s "  s.Close();~%")
        (format s "  // Compare~%  Test(msg1.Length==SLPacket.~aLength(msg2), \"Length differ: \"+msg1.Length+\" \"+SLPacket.~aLength(msg2));~%" name name)
        (format s "  for(int i=1; i<msg1.Length; i++) Test(msg1[i]==msg2[i], \"msg differ in position \"+i+\", \"+msg1[i]+\" != \"+msg2[i]);~%")
        (format s "}~%" (packet-name packet))))))

(defun c#-unittest-function-calls (parse-tree) 
  (with-output-to-string (s)
    (dolist (packet parse-tree)
      (format s "Test~A();~%" (packet-name packet)))))

(defun make-c#-code(parse-tree namespace)
  (let ((packet-template (read-file "template.cs"))
        (unittest-template (read-file "template-unittest.cs"))
        (enums (c#-packet-enums parse-tree))
        (packet-functions (c#-packet-udp-functions parse-tree))
        (unittest-functions (c#-unittest-functions parse-tree))
        (unittest-function-calls (c#-unittest-function-calls parse-tree)))
    (with-open-file (f "funsl.cs" :direction :output :if-exists :supersede)
      (setf packet-template (replace-all packet-template "%NAMESPACE%" namespace))
      (setf packet-template (replace-all packet-template "%ENUMS%" enums))
      (setf packet-template (replace-all packet-template "%PACKETS%" packet-functions))
      (princ packet-template f))
    (with-open-file (f "funsl-unittest.cs" :direction :output :if-exists :supersede)
      (setf unittest-template (replace-all unittest-template "%NAMESPACE%" namespace))
      (setf unittest-template (replace-all unittest-template "%UNITTESTS%" unittest-functions))
      (setf unittest-template (replace-all unittest-template "%UNITTESTLIST%" unittest-function-calls))
      (princ unittest-template f))))

